package org.ddl.core.aop;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.ddl.core.annotation.DataSourceShard;
import org.ddl.core.annotation.DataSourceShardOn;
import org.ddl.core.annotation.DefaultDataSource;
import org.ddl.core.annotation.TableShard;
import org.ddl.core.annotation.TableShardOn;
import org.ddl.core.context.ShardContext;
import org.ddl.core.entity.DataSourceTable;
import org.ddl.core.strategy.DataSourceShardStrategy;
import org.ddl.core.strategy.TableShardStrategy;
import org.ddl.core.utils.ReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 分库分表AOP
 * 
 * @author yinjianwei
 * @date 2017年9月10日 下午9:11:55
 *
 */
public class ShardMultipleDataSourceAspect {

	private final Logger logger = LoggerFactory.getLogger(ShardMultipleDataSourceAspect.class);
	private ShardContext shardContext;

	/**
	 * 拦截dao层方法
	 * 
	 * @param proceedingJoinPoint
	 * @throws Throwable
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		Signature signature = proceedingJoinPoint.getSignature();
		MethodSignature methodSignature = null;
		if (!(signature instanceof MethodSignature)) {
			throw new IllegalArgumentException("只能用于方法");
		}
		methodSignature = (MethodSignature) signature;
		Method method = methodSignature.getMethod();
		Class clzss = methodSignature.getDeclaringType();
		String dataSourceKey = "";
		String tableName = "";
		String logicTable = "";
		// 1.默认数据源 - @DefaultDataSource（方法注解）
		if (method.isAnnotationPresent(DefaultDataSource.class)) {
			DefaultDataSource defaultDataSource = method.getAnnotation(DefaultDataSource.class);
			dataSourceKey = defaultDataSource.value();
			logger.info(method.getName() + "方法配置默认数据源" + dataSourceKey);
		}
		// 2.分库 - @DataSourceShard
		if (StringUtils.isBlank(dataSourceKey) && clzss.isAnnotationPresent(DataSourceShard.class)) {
			// 分库策略
			DataSourceShard dataSourceShard = (DataSourceShard) clzss.getAnnotation(DataSourceShard.class);
			String dataSourceShardType = dataSourceShard.dataSourceShardType();
			DataSourceShardStrategy dataSourceShardStrategy = shardContext
					.getDataSourceShardStrategy(dataSourceShardType);
			// 获取分库字段值
			long dataSourceShardValue = getDataSourceShardValue(proceedingJoinPoint, method);
			// 计算分库
			DataSourceTable dataSourceTable = dataSourceShardStrategy.handle(dataSourceShard.logicDataSource(),
					dataSourceShard.dataSourceShardNum(), dataSourceShardValue);
			dataSourceKey = dataSourceTable.getDataSourceKey();
			logger.info(method.getName() + "方法分库键" + dataSourceKey);
		}
		// 3.分表 - @TableShard
		if (clzss.isAnnotationPresent(TableShard.class)) {
			// 分表策略
			TableShard tableShard = (TableShard) clzss.getAnnotation(TableShard.class);
			String tableShardType = tableShard.tableShardType();
			TableShardStrategy tableShardStrategy = shardContext.getTableShardStrategy(tableShardType);
			logicTable = tableShard.logicTable();
			// 获取分表字段值
			long tableShardValue = getTableShardValue(proceedingJoinPoint, method);
			// 计算分表
			DataSourceTable dataSourceTable = tableShardStrategy.handle(tableShard.logicTable(),
					tableShard.tableShardNum(), tableShardValue);
			tableName = dataSourceTable.getTableName();
			logger.info(method.getName() + "方法，逻辑表名" + logicTable + "，真实表名" + tableName);
		}
		// 4.设置分库分表上下文配置
		DataSourceTable dataSourceTable = new DataSourceTable();
		dataSourceTable.setDataSourceKey(dataSourceKey);
		dataSourceTable.setTableName(tableName);
		dataSourceTable.setLogicTable(logicTable);
		ShardContext.setCurrentDataSourceTableContext(dataSourceTable);
		// 5.继续业务代码
		return proceedingJoinPoint.proceed();
	}

	/**
	 * 获取分库字段值
	 * 
	 * @param proceedingJoinPoint
	 * @param method
	 * @return
	 */
	private long getDataSourceShardValue(ProceedingJoinPoint proceedingJoinPoint, Method method) {
		String dataSourceShardKey = null;
		int index = -1;
		// 获取DataSourceShardOn注解，并定位参数index
		Annotation[][] parameterAnnotationsTwoArr = method.getParameterAnnotations();
		for (int i = 0; i < parameterAnnotationsTwoArr.length; i++) {
			Annotation[] parameterAnnotationsOneArr = parameterAnnotationsTwoArr[i];
			for (Annotation parameterAnnotation : parameterAnnotationsOneArr) {
				if (parameterAnnotation instanceof DataSourceShardOn) {
					DataSourceShardOn dataSourceShardOn = (DataSourceShardOn) parameterAnnotation;
					dataSourceShardKey = dataSourceShardOn.value();
					index = i;
				}
			}
		}
		// 获取参数值
		Object[] args = proceedingJoinPoint.getArgs();
		Object arg = null;
		if (index >= 0) {
			arg = args[index];
		}
		// 获取分库字段值
		long dataSourceShardValue = -1L;
		if (StringUtils.isBlank(dataSourceShardKey)) {
			dataSourceShardValue = Long.parseLong(arg.toString());
		} else {
			Object dataSourceShardValueObj = ReflectionUtils.getFieldValue(arg, dataSourceShardKey);
			dataSourceShardValue = Long.parseLong(dataSourceShardValueObj.toString());
		}
		return dataSourceShardValue;
	}

	/**
	 * 获取分表字段值
	 * 
	 * @param proceedingJoinPoint
	 * @param method
	 * @return
	 */
	private long getTableShardValue(ProceedingJoinPoint proceedingJoinPoint, Method method) {
		String tableShardKey = null;
		int index = -1;
		// 获取TableShardOn注解，并定位参数index
		Annotation[][] parameterAnnotationsTwoArr = method.getParameterAnnotations();
		for (int i = 0; i < parameterAnnotationsTwoArr.length; i++) {
			Annotation[] parameterAnnotationsOneArr = parameterAnnotationsTwoArr[i];
			for (Annotation parameterAnnotation : parameterAnnotationsOneArr) {
				if (parameterAnnotation instanceof TableShardOn) {
					TableShardOn tableShardOn = (TableShardOn) parameterAnnotation;
					tableShardKey = tableShardOn.value();
					index = i;
				}
			}
		}
		// 获取参数值
		Object[] args = proceedingJoinPoint.getArgs();
		Object arg = null;
		if (index >= 0) {
			arg = args[index];
		}
		// 获取分库字段值
		long tableShardValue = -1L;
		if (StringUtils.isBlank(tableShardKey)) {
			tableShardValue = Long.parseLong(arg.toString());
		} else {
			Object tableShardValueObj = ReflectionUtils.getFieldValue(arg, tableShardKey);
			tableShardValue = Long.parseLong(tableShardValueObj.toString());
		}
		return tableShardValue;
	}
}
